package com.opensource.lzx.utils;

import com.alibaba.fastjson.JSON;
import com.opensource.lzx.annotation.GeneralValid;
import com.opensource.lzx.enums.ValidType;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Stream;

/**
 * @Author: 739891618@qq.com
 * @Date: 2022-11-15 23:04:57
 * @ClassName: GeneraValidUtils
 * @Describe: this is a ...
 */
public class GeneraValidUtils {

    public static<T> void generalValid(T obj) {
        if (Objects.isNull(obj)) {
            throw new RuntimeException("Check obj is empty.");
        }
        doValid(obj);
    }

    private static <T> void doValid(T obj) {
        if (Objects.isNull(obj)) {
            return;
        }
        if (obj instanceof Collection && ((Collection<?>) obj).size() >= 1) {
            Iterator<?> iterator = ((Collection<?>) obj).iterator();
            while (iterator.hasNext()) {
                Object iteVal = iterator.next();
                if (isJavaClass(iteVal)) {
                    return;
                }
                doValid(iteVal);
            }
        }
        if (!isJavaClass(obj)) {
            doSingleCheck(obj);
        }
    }

    private static <T> void doSingleCheck(T obj) {
        for (Class<?> clazz = obj.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            Stream.of(clazz.getDeclaredFields())
                    .filter(GeneraValidUtils::isNeedCheck)
                    .forEach(field -> {
                        field.setAccessible(true);

                        Object valueByField = getValueByField(obj, field);
                        doValueCheck(valueByField, field);
                        if (isNeedRecursion(valueByField)) {
                            doValid(valueByField);
                        }
                    });
        }
    }

    private static void doValueCheck(Object value, Field field) {
        GeneralValid annotation = field.getAnnotation(GeneralValid.class);

        for (ValidType validType : annotation.valid()) {
            if (validType == ValidType.EMPTY_VALID) {
                doNullCheck(value, field);
            }

            if (validType == ValidType.RANGE_VALID) {
                doRangeCheck(value, field, annotation.range());
            }
        }
    }

    private static void doRangeCheck(Object value, Field field, String... rangeValues) {
        if (Objects.isNull(value) || rangeValues.length == 0) {
            return;
        }
        if (!StringUtils.equalsAny(value.toString(), rangeValues)) {
            throw new RuntimeException("The field: " + field.getName() + " not in " + JSON.toJSONString(rangeValues));
        }
    }

    private static void doNullCheck(Object value, Field field) {
        if (Objects.isNull(value)) {
            throw new RuntimeException("The field: " + field.getName() + " is not allowed empty.");
        }
        if (value instanceof String && StringUtils.isBlank(value.toString())) {
            throw new RuntimeException("The field: " + field.getName() + " is not allowed empty.");
        }

        if (value instanceof Collection && ((Collection<?>) value).size() <= 0) {
            throw new RuntimeException("The field: " + field.getName() + " is not allowed empty.");
        }

        if (value instanceof Object[] && ((Object[]) value).length <= 0) {
            throw new RuntimeException("The field: " + field.getName() + " is not allowed empty.");
        }
    }

    private static boolean isNeedRecursion(Object value) {
        if (value instanceof List && !isJavaClass(((List<?>) value).get(0))) {
            return true;
        }
        return !isJavaClass(value);
    }

    private static<T> Object getValueByField(T obj, Field field) {
        try {
            return field.get(obj);
        } catch (IllegalAccessException e) {
            return null;
        }
    }

    private static boolean isNeedCheck(Field field) {
        return field.isAnnotationPresent(GeneralValid.class);
    }

    private static<T> boolean isJavaClass(T obj) {
        return Objects.nonNull(obj) && obj.getClass().getClassLoader() == null;
    }
}
